 /*******************************************************************************
  * Copyright (c) 2004, 2006 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.ui.internal.intro.impl.swt;

 import java.util.Enumeration ;
 import java.util.Hashtable ;
 import java.util.Properties ;

 import org.eclipse.core.runtime.FileLocator;
 import org.eclipse.core.runtime.Path;
 import org.eclipse.jface.resource.JFaceResources;
 import org.eclipse.swt.graphics.Color;
 import org.eclipse.swt.graphics.Font;
 import org.eclipse.swt.graphics.Image;
 import org.eclipse.ui.forms.widgets.FormToolkit;
 import org.eclipse.ui.internal.intro.impl.model.AbstractBaseIntroElement;
 import org.eclipse.ui.internal.intro.impl.model.AbstractIntroContainer;
 import org.eclipse.ui.internal.intro.impl.model.AbstractIntroElement;
 import org.eclipse.ui.internal.intro.impl.model.AbstractIntroIdElement;
 import org.eclipse.ui.internal.intro.impl.model.AbstractIntroPage;
 import org.eclipse.ui.internal.intro.impl.model.IntroGroup;
 import org.eclipse.ui.internal.intro.impl.model.IntroImage;
 import org.eclipse.ui.internal.intro.impl.model.IntroLink;
 import org.eclipse.ui.internal.intro.impl.model.IntroModelRoot;
 import org.eclipse.ui.internal.intro.impl.model.IntroText;
 import org.eclipse.ui.internal.intro.impl.model.loader.ModelLoaderUtil;
 import org.eclipse.ui.internal.intro.impl.util.ImageUtil;
 import org.eclipse.ui.internal.intro.impl.util.Log;
 import org.osgi.framework.Bundle;

 public class PageStyleManager extends SharedStyleManager {

     private AbstractIntroPage page;
     private Hashtable altStyleContexts = new Hashtable ();
     private IntroModelRoot root;


     /**
      * Constructor used when page styles need to be loaded. The plugin's bundle
      * is retrieved from the page model class. The default properties are
      * assumed to be the presentation shared properties. The inherrited
      * properties are properties that we got from included and extension styles.
      *
      * @param modelRoot
      */
     public PageStyleManager(AbstractIntroPage page, Properties sharedProperties) {
         this.page = page;
         context = new StyleContext();
         context.bundle = page.getBundle();
         
         // honor shared-style.
 if (page.injectSharedStyle())
             properties = new Properties (sharedProperties);
         else
             properties = new Properties ();
         String altStyle = page.getAltStyle();
         if (altStyle != null) {
             load(properties, altStyle, context);
         }

         // AltStyles Hashtable has alt-styles as keys, the bundles as
 // values.
 Hashtable altStyles = page.getAltStyles();
         if (altStyles != null) {
             Enumeration styles = altStyles.keys();
             while (styles.hasMoreElements()) {
                 String style = (String ) styles.nextElement();
                 Properties inheritedProperties = new Properties ();
                 Bundle bundle = (Bundle) altStyles.get(style);
                 StyleContext sc = new StyleContext();
                 sc.bundle = bundle;
                 load(inheritedProperties, style, sc);
                 altStyleContexts.put(inheritedProperties, sc);
             }
         }

         // cache root
 root = (IntroModelRoot) page.getParentPage().getParent();
     }


     // Override parent method to include alt-styles. Use implicit keys as well.
 public String getProperty(String key) {
         return getProperty(key, true);
     }

     // Override parent method to include alt-styles. If useImplicit is true, we
 // try to resolve a key without its pageId.
 private String getProperty(String key, boolean useImplicitKey) {
         Properties aProperties = findPropertyOwner(key);
         String value = super.doGetProperty(aProperties, key);
         if (useImplicitKey) {
             if (value == null && page.getId() != null
                     && key.startsWith(page.getId())) {
                 // did not find the key as-is. Trim pageId and try again.
 String relativeKey = key.substring(page.getId().length());
                 return getProperty(relativeKey);
             }
         }
         return value;
     }


     /**
      * Finds a Properties that represents an inherited shared style, or this
      * current pages style. If the given key is not found, the pageId is trimmed
      * from the begining of the key, and the key is looked up again. If key does
      * not start with a pageId, lookup only the key as is.
      *
      * @param key
      * @return
      */
     private Properties findPropertyOwner(String key) {
         // search for the key in this page's properties first.
 if (properties.containsKey(key))
             return properties;

         // search inherited properties second.
 Enumeration inheritedPageProperties = altStyleContexts.keys();
         while (inheritedPageProperties.hasMoreElements()) {
             Properties aProperties = (Properties ) inheritedPageProperties
                 .nextElement();
             if (aProperties.containsKey(key))
                 return aProperties;
         }
         // we did not find the key. Return the local properties anyway.
 return properties;
     }



     /**
      * Finds the context from which this key was loaded. If the key is not from
      * an inherited alt style, then use the context corresponding to this page.
      *
      * @param key
      * @return
      */
    
     protected StyleContext getAssociatedContext(String key) {
         Properties aProperties = findPropertyOwner(key);
         StyleContext context = (StyleContext) altStyleContexts.get(aProperties);
         if (context != null)
             return context;
         return super.getAssociatedContext(key);
     }


     /*
      * For number of columns, do not return 1 as the default, to allow for
      * further processing. At the root page level, getting a 0 as ncolumns means
      * that the number of columns is the number of children. At the page level,
      * default is 1.
      */
     public int getPageNumberOfColumns() {
         return getIntProperty(page, ".layout.ncolumns", 0); //$NON-NLS-1$
 }


     public int getNumberOfColumns(IntroGroup group) {
         return getIntProperty(group, ".layout.ncolumns", 0); //$NON-NLS-1$
 }
     
     public boolean getEqualWidth(IntroGroup group) {
         return getBooleanProperty(group, ".layout.equalWidth", false); //$NON-NLS-1$
 }

     public int getPageVerticalSpacing() {
         return getIntProperty(page, ".layout.vspacing", 5); //$NON-NLS-1$
 }

     public int getVerticalSpacing(IntroGroup group) {
         return getIntProperty(group, ".layout.vspacing", 5); //$NON-NLS-1$
 }

     public int getPageHorizantalSpacing() {
         return getIntProperty(page, ".layout.hspacing", 5); //$NON-NLS-1$
 }

     public int getHorizantalSpacing(IntroGroup group) {
         return getIntProperty(group, ".layout.hspacing", 5); //$NON-NLS-1$
 }

     public int getColSpan(AbstractBaseIntroElement element) {
         return getIntProperty(element, ".layout.colspan", 1); //$NON-NLS-1$
 }

     public int getRowSpan(AbstractBaseIntroElement element) {
         return getIntProperty(element, ".layout.rowspan", 1); //$NON-NLS-1$
 }

     private int getIntProperty(AbstractBaseIntroElement element,
             String qualifier, int defaultValue) {
         StringBuffer buff = ModelLoaderUtil.createPathToElementKey(element, true);
         if (buff == null)
             return defaultValue;
         String key = buff.append(qualifier).toString();
         return getIntProperty(key, defaultValue);
     }
     
     private boolean getBooleanProperty(AbstractBaseIntroElement element,
             String qualifier, boolean defaultValue) {
         StringBuffer buff = ModelLoaderUtil.createPathToElementKey(element, true);
         if (buff == null)
             return defaultValue;
         String key = buff.append(qualifier).toString();
         return getBooleanProperty(key, defaultValue);
     }

     private int getIntProperty(String key, int defaulValue) {
         int intValue = defaulValue;
         String value = getProperty(key);
         if (value == null)
             return intValue;

         try {
             intValue = Integer.parseInt(value);
         } catch (NumberFormatException e) {
             Log.error("Failed to parse key: " + key + " as an integer.", e); //$NON-NLS-1$ //$NON-NLS-2$
 }
         return intValue;
     }
     
     private boolean getBooleanProperty(String key, boolean defaultValue) {
         boolean booleanValue = defaultValue;
         String value = getProperty(key);
         if (value != null)
             booleanValue = value.equalsIgnoreCase("true"); //$NON-NLS-1$
 return booleanValue;
     }


     /**
      * Finds the description text of the given group. Looks for the Text child
      * element whos id is specified as follows:
      * <p>
      * <pageId>. <path_to_group>.description-id= <id of child description Text
      * element>
      * </p>
      * If not found, use the default description style.
      *
      * Returns null if no default style found, or any id in path is null.
      *
      * @param group
      * @return
      */
     public String getDescription(IntroGroup group) {
         StringBuffer buff = ModelLoaderUtil.createPathToElementKey(group, true);
         if (buff == null)
             return null;
         String key = buff.append(".description-id").toString(); //$NON-NLS-1$
 return doGetDescription(group, key);
     }


     /**
      * Finds the description text of the associated page. Looks for the Text
      * child element whos id is specified as follows:
      * <p>
      * <pageId>.description-id= <id of child description Text element>
      * </p>
      * If not found, use the default description style.
      *
      * Returns null if no default style found, or any id in path is null.
      *
      * @param group
      * @return
      */
     public String getPageDescription() {
         if (page.getId() == null)
             return null;
         String key = page.getId() + ".description-id"; //$NON-NLS-1$
 return doGetDescription(page, key);
     }

     private String doGetDescription(AbstractIntroContainer parent, String key) {
         String path = getProperty(key);
         String description = null;
         if (path != null)
             description = findTextFromPath(parent, path);
         if (description != null)
             return description;
         return findTextFromStyleId(parent, getDescriptionStyleId());
     }

     private String getDescriptionStyleId() {
         String key = "description-style-id"; //$NON-NLS-1$
 return getProperty(key);
     }

     /**
      * Finds the subtitle of the associated page. Looks for the Text child
      * element whose id is specified as follows:
      * <p>
      * <pageId>.description-id= <id of child description Text element>
      * </p>
      * If not found, use the default description style.
      *
      * @param group
      * @return
      */
     public String getPageSubTitle() {
         String key = page.getId() + ".subtitle-id"; //$NON-NLS-1$
 String path = getProperty(key);
         String description = null;
         if (path != null)
             description = findTextFromPath(page, path);
         if (description != null)
             return description;
         return findTextFromStyleId(page, getPageSubTitleStyleId());
     }

     private String getPageSubTitleStyleId() {
         String key = "subtitle-style-id"; //$NON-NLS-1$
 return getProperty(key);
     }

     private String findTextFromPath(AbstractIntroContainer parent, String path) {
         AbstractIntroElement child = parent.findTarget(root, path);
         if (child != null && child.isOfType(AbstractIntroElement.TEXT)) {
             makeFiltered(child);
             return ((IntroText) child).getText();
         }
         return null;
     }

     /**
      * Returns the first direct child text element with the given style-id.
      *
      * @return
      */
     private String findTextFromStyleId(AbstractIntroContainer parent,
             String styleId) {
         IntroText[] allText = (IntroText[]) parent
             .getChildrenOfType(AbstractIntroElement.TEXT);
         for (int i = 0; i < allText.length; i++) {
             if (allText[i].getStyleId() == null)
                 // not all elements have style id.
 continue;
             if (allText[i].getStyleId().equals(styleId)) {
                 makeFiltered(allText[i]);
                 return allText[i].getText();
             }
         }
         return null;
     }

     /**
      * Util method to check model type, and filter model element out if it is of
      * the correct type.
      *
      * @param element
      */
     private AbstractIntroElement makeFiltered(AbstractIntroElement element) {
         if (element.isOfType(AbstractIntroElement.BASE_ELEMENT))
             ((AbstractBaseIntroElement) element).setFilterState(true);
         return element;
     }



     public boolean getShowLinkDescription() {
         String key = page.getId() + ".show-link-description"; //$NON-NLS-1$
 String value = getProperty(key);
         if (value == null) {
             key = ".show-link-description"; //$NON-NLS-1$
 value = getProperty(key);
         }
         if (value == null)
             value = "true"; //$NON-NLS-1$
 return value.toLowerCase().equals("true"); //$NON-NLS-1$
 }

     public boolean showHomePageNavigation() {
         String key = page.getId() + ".show-home-page-navigation"; //$NON-NLS-1$
 String value = getProperty(key);
         if (value == null) {
             key = ".show-home-page-navigation"; //$NON-NLS-1$
 value = getProperty(key);
         }
         if (value == null)
             value = "true"; //$NON-NLS-1$
 return value.equalsIgnoreCase("true"); //$NON-NLS-1$
 }


     public Color getColor(FormToolkit toolkit, AbstractBaseIntroElement element) {
         StringBuffer buff = ModelLoaderUtil.createPathToElementKey(element, true);
         if (buff == null)
             return null;
         String key = buff.append(".font.fg").toString(); //$NON-NLS-1$
 return getColor(toolkit, key);
     }
     
     public Color getBackgrond(FormToolkit toolkit, AbstractBaseIntroElement element) {
         StringBuffer buff = ModelLoaderUtil.createPathToElementKey(element, true);
         if (buff == null)
             return null;
         String key = buff.append(".bg").toString(); //$NON-NLS-1$
 return getColor(toolkit, key);
     }

     public boolean isBold(IntroText text) {
         String value = null;
         /*
         StringBuffer buff = ModelLoaderUtil.createPathToElementKey(text, true);
         if (buff != null) {
             String key = buff.append(".font.bold").toString(); //$NON-NLS-1$
             value = getProperty(key);
             if (value != null)
                 return value.toLowerCase().equals("true"); //$NON-NLS-1$
             else {
                 buff = ModelLoaderUtil.createPathToElementKey(text, true);
             }
         }
         */
         value = getPropertyValue(text, ".font.bold"); //$NON-NLS-1$
 if (value == null) {
             // bold is not specified by ID. Check to see if there is a style-id
 // specified for bold.
 value = getProperty("bold-style-id"); //$NON-NLS-1$
 if (value != null && text.getStyleId() != null)
                 return text.getStyleId().equals(value);
         }
         return false;
     }
     
     private String getPropertyValue(AbstractIntroIdElement element, String suffix) {
         StringBuffer buff = ModelLoaderUtil.createPathToElementKey(element, true);
         if (buff != null) {
             String key = buff.append(suffix).toString();
             String value = getProperty(key);
             if (value != null)
                 return value;
             // try the page.id key
 buff = ModelLoaderUtil.createPathToElementKey(element, false);
             if (buff!= null) {
                 key = buff.append(suffix).toString();
                 value = getProperty(key);
                 return value;
             }
         }
         return null;
     }

     public static Font getBannerFont() {
         return JFaceResources.getBannerFont();
     }

     public static Font getHeaderFont() {
         return JFaceResources.getHeaderFont();
     }

     /**
      * Retrieves an image for a link in a page. If not found, uses the page's
      * default link image. If still not found, uses the passed default.
      *
      * @param link
      * @param qualifier
      * @return
      */
     public Image getImage(IntroLink link, String qualifier, String defaultKey) {
         // try the Id first
 String key = createImageByIdKey(page, link, qualifier);
         String value = getProperty(key, false);
         if (value==null) {
             key = createImageKey(page, link, qualifier);
             // special case where we have to handle this because extended code does
 // not go through getProperty() in this method.
 value = getProperty(key, false);
         }
         if (value == null && page.getId() != null
                 && key.startsWith(page.getId()))
             // did not use the key as-is. Trim pageId and try again.
 key = key.substring(page.getId().length());

         // pageKey can not become an implicit key.
 String pageKey = createImageKey(page, null, qualifier);

         return getImage(key, pageKey, defaultKey);
     }

     private String createImageKey(AbstractIntroPage page, IntroLink link,
             String qualifier) {
         StringBuffer buff = null;
         if (link != null) {
             buff = ModelLoaderUtil.createPathToElementKey(link, true);
             if (buff == null)
                 return ""; //$NON-NLS-1$
 } else {
             buff = new StringBuffer ();
             buff.append(page.getId());
         }
         buff.append("."); //$NON-NLS-1$
 buff.append(qualifier);
         return buff.toString();
     }
     
     private String createImageByIdKey(AbstractIntroPage page, IntroLink link,
             String qualifier) {
         if (link==null || link.getId()==null)
             return ""; //$NON-NLS-1$
 StringBuffer buff = new StringBuffer ();
         buff.append(page.getId());
         buff.append("."); //$NON-NLS-1$
 buff.append(link.getId());
         buff.append("."); //$NON-NLS-1$
 buff.append(qualifier);
         return buff.toString();
     }

     public Image getImage(IntroImage introImage) {
         String imageLocation = introImage.getSrcAsIs();
         StringBuffer buff = ModelLoaderUtil.createPathToElementKey(introImage, true);
         String key;
         if (buff == null) {
             key = "//" + imageLocation; //$NON-NLS-1$
 } else {
             key = buff!=null?buff.toString():null;
         }
         if (ImageUtil.hasImage(key))
             return ImageUtil.getImage(key);
         // key not already registered.
 if (buff != null) {
             StyleContext acontext = getAssociatedContext(key);
             if (acontext.inTheme) {
                 ImageUtil.registerImage(key, acontext.path, imageLocation);
                 return ImageUtil.getImage(key);
             }
         }
         Bundle bundle = introImage.getBundle();
         if (FileLocator.find(bundle, new Path(imageLocation), null) == null) {
             return null;
         }
         ImageUtil.registerImage(key, bundle, imageLocation);
         return ImageUtil.getImage(key);

     }


 }

